/**
 * @file       ili9488.c
 * @author     wangbing
 * @email      mz8023yt@163.com
 * @version    V1.0
 * @date       2022-03-08
 * @brief      ips lcd panel drvier
 */

#include "ili9488.h"
#include <stdio.h>
#include <string.h>

/**
 * reg0x3A: COLMOD Interface Pixel Format
 *
 * +--------------------------------------------------------------------------------------+
 * |           | D/CS | RDX | WRX | D[23:8] | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | HEX |
 * |-----------+------+-----+-----+---------+----+----+----+----+----+----+----+----+-----|
 * |  Command  |  0   |  1  |     |    XX   | 0  | 0  | 1  | 1  | 1  | 0  | 1  | 0  | 3Ah |
 * |-----------+------+-----+-----+---------+----+----+----+----+----+----+----+----+-----|
 * | 1st param |  1   |  1  |     |    XX   | X  |   DPI[2:0]   |   DBI[2:0]   | X  | XX  |
 * +--------------------------------------------------------------------------------------+
 *
 */

void _rgb565_to_rgb888(uint16_t rgb565, uint8_t* r, uint8_t* g, uint8_t* b)
{
	*r = ((rgb565 >> 11) & 0x1f) << 3;
	*g = ((rgb565 >> 5) & 0x3f) << 2;
	*b = ((rgb565 >> 0) & 0x1f) << 3;
}

void ili9488_init(struct ili9488* lcd)
{
	int i = 0;

	/* reset sequence */
	/* the spec requires at least 10us */
	lcd->ops.reset(HIGH);
	lcd->ops.msleep(200);

	lcd->ops.reset(LOW);
	lcd->ops.msleep(200);

	/* the spec requires a maximum wait of 120ms */
	lcd->ops.reset(HIGH);
	lcd->ops.msleep(200);

	uint32_t n = 0;
	struct cmds init_cmds[] = {
		{ 0XF7, { 0xA9, 0x51, 0x2C, 0x82 }, 4 },
		{ 0XEC, { 0x00, 0x02, 0x03, 0x7A }, 4 },
		{ 0xC0, { 0x13, 0x13 }, 2 },
		{ 0xC1, { 0x41 }, 1 },
		{ 0xC5, { 0x00, 0x28, 0x80 }, 3 },
		{ 0xB1, { 0xB0, 0x11 }, 2 },
		{ 0xB4, { 0x02 }, 1 },
		{ 0xB6, { 0x02, 0x22 }, 2 },
		{ 0xB7, { 0xC6 }, 1 },
		{ 0xBE, { 0x00, 0x04 }, 2 },
		{ 0xE9, { 0x00 }, 1 },
		{ 0xF4, { 0x00, 0x00, 0x0F }, 3 },
		{ 0xE0, { 0x00, 0x04, 0x0E, 0x08, 0x17, 0x0A, 0x40, 0x79, 0x4D, 0x07, 0x0E, 0x0A, 0x1A, 0x1D, 0x0F }, 15 },
		{ 0xE1, { 0x00, 0x1B, 0x1F, 0x02, 0x10, 0x05, 0x32, 0x34, 0x43, 0x02, 0x0A, 0x09, 0x33, 0x37, 0x0F }, 15 },
		{ 0xF4, { 0x00, 0x00, 0x0F }, 3 },
		{ 0x36, { 0x08 }, 1 },
		{ 0x3A, { 0x66 }, 1 },
		{ 0x11, { 0 }, CMDS_NODATS },
		{ 0x00, { 120 }, CMDS_DELAY },
		{ 0x29, { 0 }, CMDS_NODATS },
		{ 0x00, { 50 }, CMDS_DELAY },
		{ 0x00, { 0 }, CMDS_END },
	};

	while (init_cmds[n].nums != CMDS_END) {
		if (CMDS_DELAY == init_cmds[n].nums) {
			lcd->ops.msleep(init_cmds[n].dats[0]);
			printf("delay %d ms\n", init_cmds[n].dats[0]);
			n++;
			continue;
		}

		lcd->ops.datacmd(LOW);
		lcd->ops.write(&init_cmds[n].cmd, 1);
		printf("command write: 0x%02x", init_cmds[n].cmd);

		if (CMDS_NODATS == init_cmds[n].nums) {
			printf("\n");
			n++;
			continue;
		}

		printf("--->");

		lcd->ops.datacmd(HIGH);
		lcd->ops.write(init_cmds[n].dats, init_cmds[n].nums);
		for (i = 0; i < init_cmds[n].nums; i++) {
			printf(" 0x%02x", init_cmds[n].dats[i]);
		}

		printf("\n");

		n++;
	}

	/* light up backlight */
	lcd->ops.backlight(HIGH);
}

void ili9488_point_draw(struct ili9488* lcd, uint16_t x, uint16_t y, uint16_t color)
{
	uint8_t xh = 0;
	uint8_t xl = 0;
	uint8_t yh = 0;
	uint8_t yl = 0;

	uint8_t r = 0;
	uint8_t g = 0;
	uint8_t b = 0;

	_rgb565_to_rgb888(color, &r, &g, &b);

	x = x + lcd->cfg.fix.xshift;
	y = y + lcd->cfg.fix.yshift;
	xh = x >> 8;
	xl = x & 0xff;
	yh = y >> 8;
	yl = y & 0xff;

	uint8_t cmds = 0x00;
	uint8_t dats[4] = { 0x00, 0x00, 0x00, 0x00 };

	/* Column Address Set */
	cmds = 0x2A;
	dats[0] = xh;
	dats[1] = xl;
	dats[2] = xh;
	dats[3] = xl;
	lcd->ops.datacmd(LOW);
	lcd->ops.write(&cmds, 1);
	lcd->ops.datacmd(HIGH);
	lcd->ops.write(dats, 4);

	/* Row Address Set */
	cmds = 0x2B;
	dats[0] = yh;
	dats[1] = yl;
	dats[2] = yh;
	dats[3] = yl;
	lcd->ops.datacmd(LOW);
	lcd->ops.write(&cmds, 1);
	lcd->ops.datacmd(HIGH);
	lcd->ops.write(dats, 4);

	/* Memory Write */
	cmds = 0x2C;
	lcd->ops.datacmd(LOW);
	lcd->ops.write(&cmds, 1);
	dats[0] = r;
	dats[1] = g;
	dats[2] = b;
	lcd->ops.datacmd(HIGH);
	lcd->ops.write(dats, 3);
}

void ili9488_rect_fill(struct ili9488* lcd, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t* color)
{

	uint8_t cmds = 0x00;
	uint8_t dats[4] = { 0x00, 0x00, 0x00, 0x00 };
	uint32_t size = 0;

	/* Column Address Set */
	cmds = 0x2A;
	dats[0] = (x1 + lcd->cfg.fix.xshift) >> 8;
	dats[1] = (x1 + lcd->cfg.fix.xshift) & 0xff;
	dats[2] = (x2 + lcd->cfg.fix.xshift) >> 8;
	dats[3] = (x2 + lcd->cfg.fix.xshift) & 0xff;
	lcd->ops.datacmd(LOW);
	lcd->ops.write(&cmds, 1);
	lcd->ops.datacmd(HIGH);
	lcd->ops.write(dats, 4);

	/* Row Address Set */
	cmds = 0x2B;
	dats[0] = (y1 + lcd->cfg.fix.yshift) >> 8;
	dats[1] = (y1 + lcd->cfg.fix.yshift) & 0xff;
	dats[2] = (y2 + lcd->cfg.fix.yshift) >> 8;
	dats[3] = (y2 + lcd->cfg.fix.yshift) & 0xff;
	lcd->ops.datacmd(LOW);
	lcd->ops.write(&cmds, 1);
	lcd->ops.datacmd(HIGH);
	lcd->ops.write(dats, 4);

	/* Memory Write */
	cmds = 0x2C;
	size = (x2 - x1 + 1) * (y2 - y1 + 1) * 3;
	lcd->ops.datacmd(LOW);
	lcd->ops.write(&cmds, 1);
	lcd->ops.datacmd(HIGH);
	lcd->ops.write((uint8_t*)color, size);
}

void ili9488_rect_fill_color(struct ili9488* lcd, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
	int i = 0;
	uint8_t cmds = 0x00;
	uint8_t dats[4] = { 0x00, 0x00, 0x00, 0x00 };
	uint8_t r = 0;
	uint8_t g = 0;
	uint8_t b = 0;
	uint32_t cnts = 0;

	_rgb565_to_rgb888(color, &r, &g, &b);

	/* Column Address Set */
	cmds = 0x2A;
	dats[0] = (x1 + lcd->cfg.fix.xshift) >> 8;
	dats[1] = (x1 + lcd->cfg.fix.xshift) & 0xff;
	dats[2] = (x2 + lcd->cfg.fix.xshift) >> 8;
	dats[3] = (x2 + lcd->cfg.fix.xshift) & 0xff;
	lcd->ops.datacmd(LOW);
	lcd->ops.write(&cmds, 1);
	lcd->ops.datacmd(HIGH);
	lcd->ops.write(dats, 4);

	/* Row Address Set */
	cmds = 0x2B;
	dats[0] = (y1 + lcd->cfg.fix.yshift) >> 8;
	dats[1] = (y1 + lcd->cfg.fix.yshift) & 0xff;
	dats[2] = (y2 + lcd->cfg.fix.yshift) >> 8;
	dats[3] = (y2 + lcd->cfg.fix.yshift) & 0xff;
	lcd->ops.datacmd(LOW);
	lcd->ops.write(&cmds, 1);
	lcd->ops.datacmd(HIGH);
	lcd->ops.write(dats, 4);

	/* Memory Write */
	cmds = 0x2C;
	cnts = (x2 - x1 + 1) * (y2 - y1 + 1);
	lcd->ops.datacmd(LOW);
	lcd->ops.write(&cmds, 1);
	lcd->ops.datacmd(HIGH);
	for (i = 0; i < cnts; i++) {
		dats[0] = r;
		dats[1] = g;
		dats[2] = b;
		lcd->ops.write(dats, 3);
	}
}
